﻿// Copyright (C) Jari Hautio <jari.hautio@iki.fi> 2008. Licensed under GPLv2. See LICENSE.txt in solution folder.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace RyijyApp
{
    /// <summary>
    /// Handles image recoloring by color change or pixel painting.
    /// Keeps undo stack 
    /// </summary>
    class Recolorer
    {        
        #region nested undo classes
        interface UndoItem
        {
            void Undo();
            ColorLib.ColorHistogram Histogram { get; }
        }
        class UndoColors : UndoItem
        {
			public UndoColors(Bitmap image, Bitmap baseImg, ColorMap[] colorMap, ColorLib.ColorHistogram h)
            {
                targetImage = image;
                baseImage = baseImg;
                map = colorMap;
                if (h!= null) hist = h.CloneHistogram();
            }
            Bitmap baseImage = null;
            Bitmap targetImage = null;
            ColorMap[] map = null;
            ColorLib.ColorHistogram hist = null;
			public ColorLib.ColorHistogram Histogram { get { return hist; } }

            public void Undo()
            {
                if (targetImage == null || baseImage == null) return;
                using (Graphics g = Graphics.FromImage(targetImage))
                {
                    if (map == null)
                    {
                        g.DrawImageUnscaled(baseImage, 0, 0);
                    }
                    else
                    {
                        System.Drawing.Imaging.ImageAttributes attr = new System.Drawing.Imaging.ImageAttributes();
                        attr.SetRemapTable(map);
                        Rectangle r = new Rectangle(0, 0, baseImage.Width, baseImage.Height);
                        g.DrawImage(baseImage, r, 0, 0, baseImage.Width, baseImage.Height, GraphicsUnit.Pixel, attr);
                    }
                }
            }
        }
        class UndoDraw : UndoItem
        {
			public UndoDraw(Bitmap image, Point pt, ColorLib.ColorHistogram h)
            {
                img = image;
                point = pt;
                color = image.GetPixel(pt.X, pt.Y);
                if (h != null) hist = h.CloneHistogram();
            }
            public void Undo()
            {
                img.SetPixel(point.X, point.Y, color);
            }
            Bitmap img;
            Point point;
            Color color;
            ColorLib.ColorHistogram hist = null;
            public ColorLib.ColorHistogram Histogram { get { return hist; } }
        }
        #endregion
        Stack<UndoItem> undoStack = new Stack<UndoItem>();
        Bitmap image = new Bitmap(1, 1);
        Bitmap original = new Bitmap(1, 1);
        /// <summary>
        /// Set or get target picture for drawing
        /// </summary>
        public Bitmap Image
        {
            get
            {
                return image;
            }
            set
            {
                image = value;
                if (image != null)
                    original = image.Clone() as Bitmap;
                if (undoStack.Count > 0)
                {
                    undoStack.Clear();
                    OnCanUndoChanged();
                }
            }
        }
        /// <summary>
        /// Color histogram
        /// </summary>
        private ColorLib.ColorHistogram histogram = null;

        /// <summary>
        /// Get or set Color histogram.
        /// If histogram is set, it will updated during operations
        /// </summary>
	    public ColorLib.ColorHistogram Histogram
	    {
		    get { return histogram;}
		    set { histogram = value;}
	    }

	        /// <summary>
        /// Called when image has been changed
        /// </summary>
        public event EventHandler ImageChanged;
        /// <summary>
        /// Fires image changed event
        /// </summary>
        void OnImageChanged()
        {
            if (ImageChanged != null)
                ImageChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Undo last image change
        /// </summary>
        public void Undo()
        {
            if (undoStack.Count > 0)
            {
                UndoItem undo = undoStack.Pop();
                undo.Undo();
                SetHistogram(undo.Histogram);
                OnImageChanged();
                if (undoStack.Count == 0)
                    OnCanUndoChanged();
            }

        }
        /// <summary>
        /// Revert to original image
        /// </summary>
        public void Revert()
        {
            using ( Graphics g = Graphics.FromImage(image) )
            {
                g.DrawImageUnscaled(original, 0, 0);
            }
            if (undoStack.Count > 0)
            {
                SetHistogram(undoStack.ToArray()[undoStack.Count-1].Histogram);
            }
            OnImageChanged();
            if (undoStack.Count > 0)
            {
                undoStack.Clear();
                OnCanUndoChanged();
            }

        }

		private void SetHistogram(ColorLib.ColorHistogram h)
        {
            if (histogram == null || h == null) return;
            histogram.Replicate(h);
        }

        /// <summary>
        /// True when there's something to undo
        /// </summary>
        public bool CanUndo { get { return undoStack.Count>0; } }
        /// <summary>
        /// Event notifying changes in CanUndo property.
        /// </summary>
        public event EventHandler CanUndoChanged;
        /// <summary>
        /// Fire CanUndo change notification
        /// </summary>
        void OnCanUndoChanged()
        {
            if (CanUndoChanged != null)
                CanUndoChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Sets pixel color
        /// </summary>
        /// <param name="x">Pixel x location</param>
        /// <param name="y">Pixel y location</param>
        /// <param name="c">new color</param>
        public void SetPixel(int x, int y, Color color)
        {
            if (x >= 0 && x < image.Width &&
                y >= 00 && y < image.Height &&
                image != null)
            {
                Color oldColor = image.GetPixel(x, y);
                if (oldColor == color) return;

                undoStack.Push(new UndoDraw(image, new Point(x, y),histogram));
                image.SetPixel(x, y, color);

                ChangeHistogram(oldColor, color, 1);
                OnImageChanged();
                if (undoStack.Count == 1)
                    OnCanUndoChanged();
            }
        }

        /// <summary>
        /// Changes histogram by replacing n occurences of oldColor with newColor.
        /// </summary>
        /// <param name="oldColor"></param>
        /// <param name="newColor"></param>
        /// <param name="n"></param>
        private void ChangeHistogram(Color oldColor, Color newColor, int n)
        {
            if (histogram != null)
            {
				histogram.AddCount(oldColor, -n);
				histogram.AddCount(newColor, n);
            }
        }

        /// <summary>
        /// Replaces all from colors with to color
        /// </summary>
        /// <param name="from">Color to be changed</param>
        /// <param name="to">New color</param>
        public void RemapColor(Color from, Color to)
        {
            List<Color> l = new List<Color>();
            l.Add(from);
            RemapColors(l, to);
        }

        /// <summary>
        /// Replaces all from colors with to color
        /// </summary>
        /// <param name="from">Colors to be changed</param>
        /// <param name="to">New color</param>
        public void RemapColors(List<Color> from, Color to)
        {
            if (from.Count == 0) return;
            undoStack.Push(new UndoColors(image, image.Clone() as Bitmap, null, histogram));
            using (Graphics g = Graphics.FromImage(image))
            {
                Rectangle r = new Rectangle(0, 0, image.Width, image.Height);
                System.Drawing.Imaging.ColorMap[] map = new System.Drawing.Imaging.ColorMap[from.Count];
                for (int i = 0; i < from.Count; i++)
                {
                    System.Drawing.Imaging.ColorMap m = new System.Drawing.Imaging.ColorMap();
                    m.OldColor = from[i];
                    m.NewColor = to;
                    map[i] = m;
                }
                System.Drawing.Imaging.ImageAttributes attr = new System.Drawing.Imaging.ImageAttributes();
                attr.SetRemapTable(map);
                g.DrawImage(image, r, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attr);
            }
            if (histogram != null) histogram.RemapColors(from, to);
            OnImageChanged();
            if (undoStack.Count == 1)
                OnCanUndoChanged();
        }
    }
}
